import _ from 'lodash'
import qs from 'qs'
import { CreateElement } from 'vue'
import { notification, Button } from 'ant-design-vue'
import { Context } from '@nuxt/types'
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'

import { ResultStatusEnum } from '@/enums/result-status.enum'
import { ACCOUNT_EXPIRE_REMIND } from '@/enums/mutation-types'

import { AppConfig } from '@/types/app-config'
import { BackendResult } from '@/types/backend-result'
import { RootState } from '@/types/root.state'
import { ConnectorResult } from '@/types/connector'

import { formatDatetime } from '@/utils/datetime.util'

interface Env extends AppConfig {
  [propName: string]: string;
}

interface ResolveResult {
  url: string;
  params: object | any[];
}

/**
 * 解析路径参数
 *
 * @param url
 * @param params
 * @returns {{url: string, params: *}}
 */
const resolvePath = (url: string, params: any): ResolveResult => {
  let result: ResolveResult
  if (_.isArray(params)) {
    result = {
      url,
      params
    }
  } else {
    const pathParams: string[] | null = url.match(/\{\w+\}/g)
    let resolveUrl: string = ''
    if (pathParams && pathParams.length > 0) {
      for (const param of pathParams) {
        // @ts-ignore
        const key: string | null = param.match(/\w+/g)[0]
        if (!_.isEmpty(key) && _.has(params, key)) {
          if (resolveUrl) {
            resolveUrl = resolveUrl.replace(param, params[key])
          } else {
            resolveUrl = url.replace(param, params[key])
          }
          delete params[key]
        }
      }
    }
    _.forEach(params, (v: any, k: string) => {
      if (_.isUndefined(v)) {
        params[k] = null
      }
    })
    result = {
      params,
      url: resolveUrl || url
    }
  }
  return result
}

export default function (ctx: Context): ConnectorResult {
  const {
    app,
    store,
    redirect
  } = ctx
  const env = ctx.env as Env
  const {
    $cookies,
    $axios
  } = app
  const builder = () => {
    const axios: AxiosInstance = $axios.create({ baseURL: env.server })

    axios.interceptors.request.use((reqConfig: AxiosRequestConfig): AxiosRequestConfig => {
      const token = $cookies.get<string>(env.cookieToken)
      if (token) {
        reqConfig.headers[env.token] = token
      }
      return reqConfig
    }, (err: any) => Promise.reject(err))

    axios.interceptors.response.use(async (response: AxiosResponse<BackendResult>): Promise<any> => {
      const headers: { [prop: string]: string } = response.headers
      const res: BackendResult = response.data
      if (res.status === ResultStatusEnum.EXPIRE) {
        await store.dispatch('user/clearUserState')
        return redirect('/login')
      } else if (_.has(headers, env.tokenRemind.toLowerCase())) {
        const rootState: RootState = store.state
        if (rootState.app.expireRemind && process.client) {
          const notifyKey: string = 'AuthExpireNotify'
          notification.warning({
            key: notifyKey,
            message: '认证过期提醒',
            description: (h: CreateElement) => {
              const btn = h(Button, {
                props: {
                  type: 'link',
                  size: 'small'
                },
                on: {
                  click: () => {
                    store.dispatch('user/clearUserState')
                    store.commit('app/' + ACCOUNT_EXPIRE_REMIND, false)
                    notification.close(notifyKey)
                    redirect('/login')
                  }
                }
              }, '重新登录')
              const text = h('span', '您的认证Token将于10分钟内过期，请及时重新登录！')
              return h('div', [text, btn])
            },
            duration: 0,
            onClose: () => {
              store.commit('app/' + ACCOUNT_EXPIRE_REMIND, false)
            }
          })
        }
      }
      return res
    }, err => Promise.reject(err))

    return axios
  }

  return {
    /**
     * 查询
     *
     * @param   {string}  url
     * @param   {object}  params
     * @return  {Promise}
     */
    query (url, params = {}): Promise<BackendResult> {
      const factory = builder()
      const resolve = resolvePath(url, _.pickBy(params, (o: any) => (_.isNumber(o) || _.isBoolean(o) || !!o)))
      const reqConfig: AxiosRequestConfig = {
        method: 'get',
        url: resolve.url,
        params: resolve.params
      }
      // 格式化查询参数日期数据格式
      _.forEach(reqConfig.params, (value: any, key: string) => {
        if (_.isDate(value)) {
          _.set(reqConfig, `params.${key}`, formatDatetime(value))
        }
      })
      return factory(reqConfig) as any
    },
    /**
     * 表单提交
     *
     * @param   {string}  url
     * @param   {object}  data
     * @param   {boolean} body
     * @return  {Promise}
     */
    post (url, data = {}, body = false): Promise<BackendResult> {
      const factory = builder()
      const resolve = resolvePath(url, data)
      const reqConfig: AxiosRequestConfig = {
        method: 'post',
        url: resolve.url,
        headers: {}
      }
      if (body) {
        reqConfig.data = resolve.params
        reqConfig.headers['Content-Type'] = 'application/json'
      } else {
        // 格式化表单参数日期数据格式
        _.forEach(resolve.params, (value: any, key: string) => {
          if (_.isDate(value)) {
            _.set(resolve, `params.${key}`, formatDatetime(value))
          }
        })
        reqConfig.data = qs.stringify(resolve.params)
        reqConfig.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
      }
      return factory(reqConfig) as any
    },
    /***
     * 上传文件
     *
     * @param url {string}
     * @param formData {FormData}
     * @param callback {function}
     * @return {Promise}
     */
    upload (
      url,
      formData = null,
      callback = null
    ): Promise<any> {
      const factory = builder()
      const reqConfig: AxiosRequestConfig = {
        url,
        data: formData,
        method: 'post',
        headers: { 'Content-Type': 'multipart/form-data' },
        onUploadProgress: (progress: any) => {
          if (callback && _.isFunction(callback)) {
            callback(progress)
          }
        }
      }
      return factory(reqConfig)
    },
    /***
     * 下载文件
     *
     * @param url {string}
     * @param params {Object}
     * @param callback {function}
     * @return {Promise}
     */
    download (
      url,
      params = {},
      callback = null
    ): Promise<any> {
      const factory = builder()
      const resolve = resolvePath(url, params)
      const reqConfig: AxiosRequestConfig = {
        url: resolve.url,
        params: resolve.params,
        method: 'get',
        responseType: 'blob',
        onDownloadProgress: (progress: any) => {
          if (callback && _.isFunction(callback)) {
            callback(progress)
          }
        }
      }
      return factory(reqConfig)
    }
  } as ConnectorResult
}
